/* Copyright (C) 2009 Vlad Roubtsov. All rights reserved. */

#include <cstddef> // size_t
using std::size_t;
#include <cstdio> // use C I/O for extra robustness inside Lua interface code
#include <exception>
#include <string>
using std::string;

#include <lua.hpp>

#include "vr/deject/component_desc.hpp"
#include "vr/exception/exceptions.hpp"
#include "vr/util/assertions.hpp"
#include "vr/util/logger.hpp"
#include "vr/util/types.hpp"
#include "vr/util/utility.hpp"

#include "vr/deject/cfg_parser.hpp"

using namespace vr::deject;
using namespace vr::exception;
using namespace vr::utility;

//----------------------------------------------------------------------------

static void
dump_ltable (lua_State * const env, const int index, const int_fast32_t depth); // forward

static void
dump_lobj (lua_State * const env, const int index, const int_fast32_t depth, const bool use_prefix)
{
    if (use_prefix) for (int_fast32_t d = 0; d < depth; ++ d) std::printf ("  ");

    const int t = lua_type (env, index);
    switch (t)
    {
        case LUA_TSTRING:
            std::printf ("\"%s\"", lua_tostring (env, index));
            break;

        case LUA_TBOOLEAN:
            std::printf ("%s", lua_toboolean (env, index) ? "true" : "false");
            break;

        case LUA_TNUMBER:
            std::printf ("%g", lua_tonumber (env, index));
            break;

        case LUA_TTABLE: // table: examine recursively
            std::printf ("table:\n");
            for (int_fast32_t d = 0; d < depth; ++ d) std::printf ("  ");
            std::printf ("{\n");

            dump_ltable (env, index, depth + 1);

            for (int_fast32_t d = 0; d < depth; ++ d) std::printf ("  ");
            std::printf ("}");
            break;

        default:
            std::printf ("<%s>", lua_typename (env, t));
            break;

    } // end of switch
}

static void
dump_ltable (lua_State * const env, const int index, const int_fast32_t depth)
{
    int top = lua_gettop (env);

    lua_pushnil (env); // first key
    while (lua_next (env, index) != 0)
    {
        // key at former top + 1, value at top + 2:
        dump_lobj (env, top + 1, depth + 1, true);
        std::printf (": ");
        dump_lobj (env, top + 2, depth + 1, false);
        std::printf ("\n");

        lua_pop (env, 1); // pop value, leave the key on the stack for the next iteration
    }
}

static void
dump_lstack (lua_State * const env)
{
    int top = lua_gettop (env);

    std::printf ("[stack depth = %i]\n", top);

    for (int index = 1; index <= top; ++ index) // examine each stack slot
    {
        std::printf ("[%i] ", index);
        dump_lobj (env, index, 0, true);
        std::printf ("\n");
    }
}
//............................................................................
//............................................................................

namespace
{

class cfg_lua_parser: noncopyable
{
    public: // ...............................................................

        cfg_lua_parser (lua_State * const env, const int /* LUA_TTABLE */ root_index) :
            m_env (env),
            m_root_index (root_index)
        {
            VR_SASSERT (env);
            VR_SASSERT (LUA_TTABLE == lua_type (env, root_index));
        }

        /**
         * stack:
         *  [m_root_index] = an assembly ltable (does not have to be @top)
         */
        auto_ptr<cfg_type>
        parse () const
        {
            dump_lstack (m_env);

            const int top = lua_gettop (m_env);
            auto_ptr<cfg_type> result (new cfg_type ());

            lua_pushnil (m_env); // push pre-first key
            while (lua_next (m_env, m_root_index) != 0) // key following the current key at top
            {
                // [component key/ID at top + 1, component descriptor ltable at top + 2]

                VR_SASSERT (LUA_TSTRING == lua_type (m_env, top + 1));
                const component_id_type ID (lua_tostring (m_env, top + 1));  // component ID

                LOG_DEBUG ("parsing [" << ID << "]");

                VR_SASSERT (LUA_TTABLE == lua_type (m_env, top + 2));
                auto_ptr<component_desc> descriptor = parse_component_descriptor (ID, top + 2);

                result->insert (ID, descriptor);

                lua_pop (m_env, 1); // pop value, leave current key on the stack for the next iteration
            }

            return result;
        }

        auto_ptr<dependency_desc>
        parse_dependency (const int /* LUA_TTABLE */ index, const int_fast32_t k) const
        {
            auto_ptr<dependency_desc> dd;

            lua_pushinteger (m_env, k);
            lua_gettable (m_env, index);
            {
                const int t = lua_type (m_env, -1);
                switch (t)
                {
                    case LUA_TSTRING:
                    {
                        const component_id_type ID (lua_tostring (m_env, -1));
                        dd = auto_ptr<dependency_desc> (new dependency_desc (ID));
                    }
                    break;

                    case LUA_TBOOLEAN:
                    {
                        const bool v (lua_toboolean (m_env, -1));
                        dd = auto_ptr<dependency_desc> (new dependency_desc (v));
                    }
                    break;

                    case LUA_TNUMBER:
                    {
                        const lua_Number v (lua_tonumber (m_env, -1)); // TODO
                        dd = auto_ptr<dependency_desc> (new dependency_desc (v));
                    }
                    break;

                    case LUA_TTABLE:
                    {
                        // (a) array of strings -> list of component references:
                        // (b) name/value pair map -> map of values
                    }
                    break;

                    default: VR_SFAIL ("unexpected dependency type")
                }
            }
            lua_pop (m_env, 1);

            return dd;
        }

        /**
         * stack:
         *  @top: component descriptor table:
         *      [1] = impl ID [string]
         *      [2] = array (int-keyed table) of dependency entries (component IDs or values)
         */
        auto_ptr<component_desc>
        parse_component_descriptor (const component_id_type & ID, const int /* LUA_TTABLE */ index) const
        {
            // impl ID:

            lua_pushinteger (m_env, 1);
            lua_gettable (m_env, index);

                VR_SASSERT (LUA_TSTRING == lua_type (m_env, -1));
                const string implID (lua_tostring (m_env, -1));

                LOG_DEBUG ("implID = [" << implID << "]");

            lua_pop (m_env, 1);

            // array table of dependencies: TODO make this optional

            lua_pushinteger (m_env, 2);
            lua_gettable (m_env, index);

                const int dep_table_index = lua_gettop (m_env);
                VR_SASSERT (LUA_TTABLE == lua_type (m_env, dep_table_index));

                // extract dependencies:

                const size_t length = lua_objlen (m_env, dep_table_index);
                ptr_vector<dependency_desc> deps (length);

                for (size_t k = 1; k <= length; ++ k)
                {
                    deps.push_back (parse_dependency (dep_table_index, k));
                }

            lua_pop (m_env, 1);

            return auto_ptr<component_desc> (new component_desc (ID, implID, deps));
        }

    private: // ..............................................................

        lua_State * const m_env;
        const int m_root_index;

}; // end of class

} // end of anonymous
//............................................................................
//............................................................................

auto_ptr<cfg_type>
cfg_parser::parse (const string & content)
{
    lua_State * env = lua_open ();
    luaL_openlibs (env);

    int lua_rc = 0;
    string lua_error;

    auto_ptr<cfg_type> result;

    try
    {
        // compile Lua chunk in 'content':
        lua_rc = luaL_loadbuffer (env, content.c_str (), content.size (), "cfg");
        // run 'content':
        lua_rc = lua_rc || lua_pcall (env, 0, 0, 0);

        if (lua_rc)
        {
            lua_error = lua_tostring (env, -1);
            lua_pop (env, 1); // pop error message
        }
        else
        {
            lua_getglobal (env, "assembly");
            VR_SASSERT (LUA_TTABLE == lua_type (env, -1));

//            dump_lstack (env);

            cfg_lua_parser parser (env, lua_gettop (env));
            result = parser.parse ();

            lua_pop (env, 1); // pop 'assembly'
        }
    }
    catch (std::exception & e)
    {
        lua_close (env);
        __VR_THROW_X (std::domain_error, "lua content parse failure: " + e.what ());
    }
    catch (...)
    {
        lua_close (env);
        __VR_THROW_X (std::domain_error, "unknown lua content parse failure");
    }

    lua_close (env);

    if (lua_rc)
        __VR_THROW_X (std::domain_error, "lua content parse failure: " + lua_error);

    return result;
}
//----------------------------------------------------------------------------
